package cn.zhangfusheng.elasticsearch.template;

import cn.zhangfusheng.elasticsearch.annotation.dsl.es.DslSearch;
import cn.zhangfusheng.elasticsearch.annotation.dsl.es.DslSortOrder;
import cn.zhangfusheng.elasticsearch.exception.GlobalSystemException;
import cn.zhangfusheng.elasticsearch.function.DslSortOrderFunction;
import cn.zhangfusheng.elasticsearch.model.annotation.DefaultDslSearch;
import cn.zhangfusheng.elasticsearch.model.es.GeoDistance;
import cn.zhangfusheng.elasticsearch.model.page.PageRequest;
import cn.zhangfusheng.elasticsearch.model.page.PageResponse;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchEntityRepositoryDetail;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * @author fusheng.zhang
 * @date 2022-02-25 11:43:40
 */
interface TemplateSearchApi extends ElasticSearchTemplateApi, Template {

    /**
     * 查询
     * @param args
     * @param entityRepositoryDetail
     * @return
     */
    default List<SearchHit> search(Object[] args, String routing, ElasticSearchEntityRepositoryDetail entityRepositoryDetail) {
        String indexName = entityRepositoryDetail.getSearchIndex();
        return this.search(args, routing, indexName);
    }

    /**
     * 查询 多条数据<br/>
     * 默认查询 10000 条,当数据超过 10000 启用滚动查询,查询全部
     * @param args    方法的参数列表
     * @param routing routing
     * @param indices 要查询的索引
     * @return
     */
    default List<SearchHit> search(Object[] args, String routing, String... indices) {
        try {
            SearchRequest searchRequest = getSearchRequest(args, routing, indices);
            ArrayList<SearchHit> searchHits = new ArrayList<>();
            this.search(searchRequest, hs -> searchHits.addAll(Arrays.asList(hs)));
            return searchHits;
        } catch (Throwable e) {
            throw new GlobalSystemException(e);
        }
    }

    /**
     * 查询
     * @param entityRepositoryDetail
     * @param pageRequest
     * @return
     */
    default PageResponse<SearchHit> searchWithPage(Object[] args, String routing,
            ElasticSearchEntityRepositoryDetail entityRepositoryDetail, PageRequest pageRequest) {
        String indexName = entityRepositoryDetail.getSearchIndex();
        return this.searchWithPage(args, routing, pageRequest, indexName);
    }

    /**
     * 分页查询
     * @param routing routing
     * @param indices 要查询的索引
     * @return 集合
     */
    default PageResponse<SearchHit> searchWithPage(
            Object[] args, String routing, PageRequest pageRequest, String... indices) {
        PageResponse<SearchHit> pageResponse = new PageResponse<>();
        List<SearchHit> searchHits = new ArrayList<>(pageRequest.getSize());
        SearchResponse searchResponse = searchWithPage(hs -> searchHits.addAll(Arrays.asList(hs)), pageRequest, args, routing, indices);
        if (Objects.nonNull(searchResponse) && !CollectionUtils.isEmpty(searchHits)) {
            Object[] sortValues = searchHits.get(searchHits.size() - 1).getSortValues();
            pageResponse.setTotal(searchResponse.getHits().getTotalHits()).setData(searchHits).setSearchAfter(sortValues);
        }
        pageResponse.setSkipTotal(pageRequest.getSkipTotal());
        return pageResponse;
    }


    /**
     * 分页查询
     * @param routing routing
     * @param indices 要查询的索引
     * @return 集合
     */
    default SearchResponse searchWithPage(
            Consumer<SearchHit[]> consumer, PageRequest pageRequest, Object[] args, String routing, String... indices) {
        try {
            SearchRequest searchRequest = getSearchRequest(args, routing, indices);
            return this.searchWithPage(searchRequest, pageRequest, consumer);
        } catch (Exception e) {
            throw new GlobalSystemException(e);
        }
    }

    default SearchRequest getSearchRequest(Object[] args, String routing, String... indices) throws Exception {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        indices = this.analysisIndex(args, indices);
        SearchRequest searchRequest = new SearchRequest(indices)
                .routing(routing).source(new SearchSourceBuilder().query(boolQueryBuilder));
        if (Objects.isNull(args) || args.length <= 0) return searchRequest;
        Iterator<Object> argsIterator = Arrays.stream(args).iterator();
        while (argsIterator.hasNext()) {
            Object arg = argsIterator.next();
            if (Objects.isNull(arg.getClass().getClassLoader())) {
                throw new GlobalSystemException("请将参数封装成自定义实体进行使用");
            }
            Field[] declaredFields = arg.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Object value = declaredField.get(arg);
                // 排序注解处理
                this.buildSort(searchRequest, declaredField, value);
                if (Objects.isNull(value)) continue;
                if (value instanceof String && StringUtils.isBlank(String.valueOf(value))) continue;
                if (value instanceof Collection && CollectionUtils.isEmpty((Collection<?>) value)) continue;
                // 查询注解解析
                this.buildSearch(boolQueryBuilder, searchRequest, declaredField, value);
            }
        }
        return searchRequest;
    }

    /**
     * {@link DslSearch} 注解的解析
     * @param boolQueryBuilder
     * @param searchRequest
     * @param declaredField    字段信息
     * @param value            字段值
     */
    default void buildSearch(
            BoolQueryBuilder boolQueryBuilder, SearchRequest searchRequest, Field declaredField, Object value) throws Exception {
        DslSearch dslSearch =
                Optional.ofNullable(declaredField.getAnnotation(DslSearch.class)).orElseGet(() -> DefaultDslSearch.INSTANCE);
        String fieldName = StringUtils.defaultIfBlank(dslSearch.value(), declaredField.getName());
        if (dslSearch.routing()) searchRequest.routing(String.valueOf(value));
        QueryBuilder queryBuilder = Objects.nonNull(declaredField.getType().getClassLoader())
                ? this.buildSearchWithEntity(searchRequest, declaredField, value)
                : dslSearch.keyword().getBuilder(fieldName, value);
        this.setQueryBuilder(dslSearch, queryBuilder, boolQueryBuilder);
    }

    /**
     * 实体解析成查询语句
     * @param searchRequest
     * @param declaredField 实体的字段
     * @param value         实体
     * @return
     * @throws Exception
     */
    default BoolQueryBuilder buildSearchWithEntity(SearchRequest searchRequest, Field declaredField, Object value) throws Exception {
        Class<?> type = declaredField.getType();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (Field declaredField_ : FieldUtils.getAllFields(type)) {
            declaredField_.setAccessible(true);
            Object value_ = declaredField_.get(value);
            this.buildSearch(boolQueryBuilder, searchRequest, declaredField_, value_);
        }
        return boolQueryBuilder;
    }

    /**
     * {@link DslSortOrder} 排序注解的解析
     * @param searchRequest
     * @param declaredField
     * @param value
     * @throws Exception
     */
    default void buildSort(SearchRequest searchRequest, Field declaredField, Object value) throws Exception {
        DslSortOrder dslSortOrder = declaredField.getAnnotation(DslSortOrder.class);
        if (Objects.nonNull(dslSortOrder)) {
            SortOrder sortOrder = dslSortOrder.value();
            Class<? extends DslSortOrderFunction> function = dslSortOrder.function();
            if (!function.equals(DslSortOrderFunction.class)) {
                sortOrder = function.newInstance().sort(value);
            }
            if (Objects.nonNull(sortOrder)) {
                String fieldName = StringUtils.defaultIfBlank(dslSortOrder.fieldName(), declaredField.getName());
                if (declaredField.getType().equals(GeoDistance.class) && Objects.nonNull(value)) {
                    GeoDistance geoDistance = (GeoDistance) value;
                    GeoDistanceSortBuilder geoDistanceSortBuilder =
                            SortBuilders.geoDistanceSort(fieldName, geoDistance.getLat(), geoDistance.getLon());
                    searchRequest.source().sort(geoDistanceSortBuilder.order(sortOrder));
                } else {
                    FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort(fieldName);
                    searchRequest.source().sort(fieldSortBuilder.order(sortOrder));
                }
            }
        }
    }

    @SuppressWarnings("UnusedReturnValue")
    default BoolQueryBuilder setQueryBuilder(DslSearch dslSearch, QueryBuilder queryBuilder, BoolQueryBuilder boolQueryBuilder) {
        switch (dslSearch.searchType()) {
            case FILTER: return boolQueryBuilder.filter(queryBuilder);
            case SHOULD: return boolQueryBuilder.should(queryBuilder);
            case MUST:
            default: return boolQueryBuilder.must(queryBuilder);
        }
    }
}
